/*
 * EasyCamera
 *
 * Copyright (c) 2020.  Frezrik frezrik@126.com
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package com.frezrik.common.thread;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * Description: Single Thread
 * Author: zhouming
 * CreateDate: 2020/6/2
 */
public class SingleThread {
    private HandlerThread mSingleThread;
    private final String mThreadName;
    public SingleHandler mThreadHandler;

    public SingleThread(String threadName) {
        mThreadName = threadName;
    }

    public final <R> R pushTaskAndReturn(SingleThread.ISingleThreadTask<R> task) throws InterruptedException {
        BlockingQueue<BlockEntity<R>> blockingQueue = new ArrayBlockingQueue(1);
        mThreadHandler.post(new SingleThread.TaskExecutor(blockingQueue, task));
        SingleThread.BlockEntity<R> data = blockingQueue.take();
        return data.getData();
    }

    public final <R> void pushSingleTask(SingleThread.ISingleThreadTask<R> task,
                                         SingleThread.IAsyncCallBack<R> callBack) {
        mThreadHandler.pushSingleTask(task, 1001, callBack);
    }

    public final <R> void pushSingleTask(SingleThread.ISingleThreadTask<R> task, int taskId,
                                         SingleThread.IAsyncCallBack<R> callBack) {
        mThreadHandler.pushSingleTask(task, taskId, callBack);
    }

    public final <R> void pushTask(SingleThread.ISingleThreadTask<R> task,
                                   SingleThread.IAsyncCallBack<R> callBack) {
        mThreadHandler.pushTask(task, callBack);
    }

    public void start() {
        this.startThread();
    }

    public final Looper getThreadLooper() {
        return mSingleThread != null ? mSingleThread.getLooper() : null;
    }

    public void stop() {
        this.stopThread();
    }

    private void startThread() {
        if (mSingleThread == null) {
            mSingleThread = new HandlerThread(mThreadName);
            mSingleThread.start();
            mThreadHandler = new SingleThread.SingleHandler(mSingleThread.getLooper());
        }

    }

    private void stopThread() {
        if (mSingleThread != null) {
            mSingleThread.quitSafely();

            try {
                mSingleThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            mSingleThread = null;
            if (mThreadHandler != null) {
                mThreadHandler.removeCallbacksAndMessages(mThreadHandler);
            }

            Log.e("TAG", "thread stop---------");
            mThreadHandler = null;
        }
    }

    static class SingleTaskObj<D> implements Runnable {
        private SingleThread.ISingleThreadTask<D> task;
        private SingleThread.IAsyncCallBack<D> callBack;

        SingleTaskObj(SingleThread.ISingleThreadTask<D> task,
                            SingleThread.IAsyncCallBack<D> callBack) {
            this.task = task;
            this.callBack = callBack;
        }

        public void run() {
            if (this.task != null) {
                D data = this.task.run();
                if (this.callBack != null) {
                    this.callBack.onCallBack(this.task, data);
                }
            } else if (this.callBack != null) {
                this.callBack.onCallBack(this.task, null);
            }

        }
    }

    static class TaskExecutor<D> implements Runnable {
        private BlockingQueue<SingleThread.BlockEntity<D>> queue;
        private SingleThread.ISingleThreadTask<D> task;

        TaskExecutor(BlockingQueue<SingleThread.BlockEntity<D>> queue,
                           SingleThread.ISingleThreadTask<D> task) {
            this.queue = queue;
            this.task = task;
        }

        public void run() {
            if (this.task != null) {
                D data = this.task.run();
                this.queue.add(new SingleThread.BlockEntity(data));
            } else {
                this.queue.add(new SingleThread.BlockEntity(null));
            }

        }
    }

    static class BlockEntity<D> {
        private D backData;

        public BlockEntity(D backData) {
            this.backData = backData;
        }

        public D getData() {
            return this.backData;
        }
    }

    static class SingleHandler extends Handler {
        private static final int WHAT_TASK = 100001;
        private static final int WHAT_SINGLE_TASK = 1001;

        SingleHandler(Looper looper) {
            super(looper);
        }

        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            try {
                Object obj = msg.obj;
                if (obj instanceof SingleThread.SingleTaskObj) {
                    SingleThread.SingleTaskObj taskObj = (SingleThread.SingleTaskObj) obj;
                    taskObj.run();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        public <R> void pushTask(SingleThread.ISingleThreadTask<R> task,
                                 SingleThread.IAsyncCallBack<R> callBack) {
            Message msg = this.obtainMessage();
            msg.what = WHAT_TASK;
            msg.obj = new SingleThread.SingleTaskObj(task, callBack);
            this.sendMessage(msg);
        }

        public <R> void pushSingleTask(SingleThread.ISingleThreadTask<R> task, int taskMsgTag,
                                       SingleThread.IAsyncCallBack<R> callBack) {
            this.removeMessages(taskMsgTag);
            Message msg = this.obtainMessage();
            msg.what = taskMsgTag;
            msg.obj = new SingleThread.SingleTaskObj(task, callBack);
            this.sendMessage(msg);
        }
    }

    public interface IAsyncCallBack<D> {
        void onCallBack(SingleThread.ISingleThreadTask<D> var1, D var2);
    }

    public abstract class ParamsSingleThreadTask<P, D> implements SingleThread.ISingleThreadTask<D> {
        private Object tag;
        private P[] params;

        public ParamsSingleThreadTask(P... params) {
            this.params = params;
        }

        public Object getTag() {
            return this.tag;
        }

        public void setTag(Object tag) {
            this.tag = tag;
        }

        public final D run() {
            return this.run(this.params);
        }

        public final P[] getParams() {
            return this.params;
        }

        public abstract D run(P[] var1);
    }

    public interface ISingleThreadTask<D> {
        D run();
    }
}